package com.ys.arch.splitamount.algorithm.core;


import com.ys.arch.splitamount.algorithm.execption.SplitAmountAlgorithmException;
import com.ys.arch.splitamount.algorithm.support.AmountAssumeTypeDetail;
import com.ys.arch.splitamount.algorithm.support.Pair;
import com.ys.arch.splitamount.algorithm.utils.AmountUtil;
import com.ys.arch.splitamount.algorithm.utils.LambdaOperateUtil;
import org.apache.commons.collections4.CollectionUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * <p>@description:拆分承担金额算法</>
 *
 * @author shuang.yang
 * @version 1.0
 * @create 2021/12/25/16:28
 */
public class SplitAssumeTypeAmountHelper {


    /**
     * @param <T>                    实体类型
     * @param list                   需要拆分承担的的原数据
     * @param function               当前行需要拆分承担的金额
     * @param assumeTypeAmountMap    总的承担金额
     * @param assumeTypePostConsumer 承担金额拆分的后置处理函数
     * @return
     */
    public static <T> List<T> splitAssumeTypeAmount(List<T> list, Function<T, BigDecimal> function,
                                                    Map<Integer/*assumeType*/, BigDecimal/*amount*/> assumeTypeAmountMap,
                                                    int scale, RoundingMode roundingMode,
                                                    BiConsumer<T, List<AmountAssumeTypeDetail>> assumeTypePostConsumer) {

        if (CollectionUtils.isEmpty(list)) {
            return new ArrayList<>();
        }
        //1 总的下标 以及对应的转换map
        int indexTotalSize = list.size();
        Map<Integer, T> transferMap = new HashMap<>();
        for (int i = 0; i < indexTotalSize; i++) {
            transferMap.put(i, list.get(i));
        }

        Map<Integer/*index*/, BigDecimal/*amount*/> indexAmountMap = transferMap
                .entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, ele -> function.apply(ele.getValue())));

        Map<Integer/*下标*/, List<AmountAssumeTypeDetail>> calculateAssumeTypeAmountMap = doCalculateAssumeTypeAmount(indexTotalSize, indexAmountMap, assumeTypeAmountMap, scale, roundingMode);

        //赋值操作
        calculateAssumeTypeAmountMap.forEach((index, assumeTypeDetails) -> {
            T t = transferMap.get(index);
            assumeTypePostConsumer.accept(t, assumeTypeDetails);
        });

        return list;

    }

    /**
     * @param indexTotalSize      条数
     * @param indexAmountMap      下标对应的金额
     * @param assumeTypeAmountMap 用于计算占比的数金额
     * @param scale               保留多少位小数
     * @param roundingMode
     * @return
     */
    public static Map<Integer/*下标*/, List<AmountAssumeTypeDetail>> doCalculateAssumeTypeAmount(Integer indexTotalSize,
                                                                                                 Map<Integer/*下标*/, BigDecimal/*下标对应的金额*/> indexAmountMap,
                                                                                                 Map<Integer/*assumeType*/, BigDecimal/*amount*/> assumeTypeAmountMap,
                                                                                                 int scale, RoundingMode roundingMode) {


        List<AmountAssumeTypeDetail> amountAssumeTypeDetails = LambdaOperateUtil.convertMap2List(assumeTypeAmountMap,
                (assumeType, amount) -> new AmountAssumeTypeDetail(amount, assumeType));
        Set<Integer> assumeTypeSet = assumeTypeAmountMap.keySet();
        //按金额升序排列
        List<Pair<Integer/*下标*/, BigDecimal/*对应的金额*/>> indexAmountPairList = LambdaOperateUtil.convertMap2ListAndSorted(indexAmountMap, Pair::of, getBigDecimalComparator());


        List<AmountAssumeTypeDetail> accAmountAssumeTypeDetailList = new ArrayList<>();
        Map<Integer/*下标*/, List<AmountAssumeTypeDetail>> indexAssumeTypeListMap = new HashMap<>();
        for (int i = 0; i < indexTotalSize; i++) {

            //基于三方支付优惠的金额进行拆分
            Pair<Integer/*下标*/, BigDecimal> indexAmountPair = indexAmountPairList.get(i);
            //将当前金额按照三方支付优惠的进行拆分
            boolean isLastItem = (indexTotalSize - 1 == i);
            List<AmountAssumeTypeDetail> collect;
            if (isLastItem) {
                //最后倒减
                collect = assumeTypeSet.stream().map(assumeType -> {
                    BigDecimal totalSubjectTypeAmount = accSubjectTypeDetailAmountFilteredBySubjectType(amountAssumeTypeDetails, assumeType, true);
                    BigDecimal accSubjectTypeAmount = accSubjectTypeDetailAmountFilteredBySubjectType(accAmountAssumeTypeDetailList, assumeType, true);
                    BigDecimal currentSubjectTypeAmount = AmountUtil.sub(totalSubjectTypeAmount, accSubjectTypeAmount);
                    return new AmountAssumeTypeDetail(currentSubjectTypeAmount, assumeType);
                }).collect(Collectors.toList());

            } else {
                //基于
                Map<Integer/*金额承担类型*/, BigDecimal/*占比返回的金额*/> splitAssumeTypeAmountMap = SplitSingleAmountHelper.splitSingleAmountAndReturnNewAbsAmountMap(indexAmountPair.getValue(),
                        assumeTypeAmountMap, scale, roundingMode);
                collect = LambdaOperateUtil.convertMap2List(splitAssumeTypeAmountMap,
                        (assumeType, amount) -> new AmountAssumeTypeDetail(amount, assumeType));

                accAmountAssumeTypeDetailList.addAll(collect);

            }

            indexAssumeTypeListMap.put(indexAmountPair.getKey(), collect);
        }


        BigDecimal accAbsAmount = indexAssumeTypeListMap.values()
                .stream()
                .map(ele -> ele.stream()
                        .map(subItem -> AmountUtil.abs(subItem.getAmount()))
                        .reduce(BigDecimal.ZERO, BigDecimal::add))
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal absTotalAmount = assumeTypeAmountMap.values()
                .stream()
                .map(AmountUtil::abs)
                .reduce(BigDecimal.ZERO, BigDecimal::add);

        if (!AmountUtil.compareIsEqual(accAbsAmount, absTotalAmount, true)) {
            throw new SplitAmountAlgorithmException("金额拆分异常-输入的总金额与拆分后累计金额不等");
        }
        return indexAssumeTypeListMap;
    }

    private static Comparator<Pair<Integer, BigDecimal>> getBigDecimalComparator() {
        return (Pair<Integer, BigDecimal> a, Pair<Integer, BigDecimal> b) -> Objects.nonNull(a) && a.getValue().compareTo(b.getValue()) > 0 ? 1 : -1;
    }

    public static BigDecimal accSubjectTypeDetailAmountFilteredBySubjectType(List<AmountAssumeTypeDetail> realRefundCostSubjectTypeDetailList,
                                                                             Integer assumeType, boolean convertNull2Zero) {


        List<BigDecimal> collect = CollectionUtils.emptyIfNull(realRefundCostSubjectTypeDetailList)
                .stream()
                .filter(ele -> Objects.equals(ele.getAssumeType(), assumeType))
                .map(AmountAssumeTypeDetail::getAmount)
                .filter(AmountUtil.predicateBigDecimalNotNullAndNotZero)
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(collect)) {
            return convertNull2Zero ? BigDecimal.ZERO : null;
        }
        return collect
                .stream()
                .reduce(BigDecimal.ZERO, BigDecimal::add);


    }

}
